#pragma once

class COperation : public COpcode
{
	friend class COperator;

protected:
	COperator				*	m_pOperator;
	CCommand				*	m_pLCommand;
	CCommand				*	m_pRCommand;
	CCommandArg				*	m_pLArg;
	CCommandArg				*	m_pRArg;
	CEnum					*	m_pLEnum;
	CEnum					*	m_pREnum;
	int							m_nLimitR;
	bool						m_bIsInverted;
	bool						m_bIsConditional;
	bool						m_bIsAssignment;

	COperation(short id, const CType * LType, const CType * RType, const CToken * pLTok = nullptr, const CToken * pRTok = nullptr)
		:	COpcode(id, OPCODE_TYPE_OPERATION),
			m_pLArg(new CCommandArg(0, LType, ARGFLAG_LEFT_OPERAND, pLTok)),
			m_pRArg(RType ? new CCommandArg(1, RType, ARGFLAG_RIGHT_OPERAND, pRTok) : nullptr),
			m_pLEnum(nullptr),
			m_pREnum(nullptr),
			m_pLCommand(nullptr),
			m_pRCommand(nullptr),
			m_bIsInverted(false),
			m_bIsConditional(false),
			m_bIsAssignment(false)
	{
	}

public:
	COperator			*	GetOperator() const								{return m_pOperator;}
	CCommandArg			*	GetLeftArg() const								{return m_pLArg;}
	CCommandArg			*	GetRightArg() const								{return m_pRArg;}
	//CCommand			*	GetLeftCommand()								{return m_pLCommand;}
	CCommand			*	GetRightCommand()								{return m_pRCommand;}
	int						GetRightSizeLimit() const						{return m_nLimitR;}
	CEnum				*	GetLeftEnum()									{return m_pLEnum;}
	CEnum				*	GetRightEnum()									{return m_pREnum;}
	
	bool					IsInverted() const								{return m_bIsInverted;}
	bool					IsConditional() const							{return m_bIsConditional;}
	
	void					SetRightSizeLimit(int i)						{m_nLimitR = i;}
	void					SetIsInverted(bool b)							{m_bIsInverted = b;}
	void					SetIsAssignment(bool b)							{m_bIsAssignment = b;}
	void					SetIsConditional(bool b)						{m_bIsConditional = b;}
	void					SetLeftCommand(CCommand *pCommand)				{m_pLCommand = pCommand;}
	void					SetRightCommand(CCommand *pCommand)				{m_pRCommand = pCommand;}
	void					SetLeftEnum(CEnum *pEnum)						{m_pLEnum = pEnum;}
	void					SetRightEnum(CEnum *pEnum)						{m_pREnum = pEnum;}
};

class COperator
{
	friend class COperators;

	char					m_bOperator[MAX_OPERATOR_CHARS];
	bool					m_bConditional;
	bool					m_bIsAssignment;
	int						m_nNumOperations;
	CLListEx<COperation*>	m_Operations;
	//COperation	*			m_pOperations[MAX_OPERATIONS];

	COperator(const char * op)
	{
		for(int i=0; i<MAX_OPERATOR_CHARS; i++)
		{
			m_bOperator[i] = op[i];
		}
		m_nNumOperations = 0;
		m_bConditional = 0;
		m_bIsAssignment = 0;
	}
	~COperator()
	{
		// cease the operation, doctor
		for(auto pLink = m_Operations.Begin(); pLink != m_Operations.Bad(); ++pLink)
		{
			delete *pLink;
		}
	}

public:
	int GetNumOperations()								{return m_nNumOperations;}

	COperation * AddOperation(WORD id, const CType * LType, const CType * RType , const CToken * pLToken = nullptr, const CToken * pRToken = nullptr)
	{
		auto pOperation = new COperation(id, LType, RType, pLToken, pRToken);
		pOperation->m_pOperator = this;
		pOperation->m_nIndex = m_nNumOperations++;
		COpcodes::Register(pOperation);
		m_Operations.Add(pOperation);
		return pOperation;
	}

	CLListEx<COperation*>					*	GetOperations() 				{return &m_Operations;}
	const CLListEx<const COperation*>		*	GetOperations()	const 			{return (CLListEx<const COperation*>*)&m_Operations;}

	//COperation * GetOperationByIndex(int idx)			{return m_nNumOperations > idx ? m_pOperations[idx] : FALSE;}
	COperation * GetOperationMatchingType(const CTypeBase * pType)
	{
		COperation * pLooseMatch = nullptr;
		for(auto pLink = m_Operations.Begin(); pLink != m_Operations.Bad(); ++pLink)
		{
			if(!pLink->IsConditional() && pLink->GetLeftArg()->IsStrictlyCompatible(pType))
				return *pLink;
			else if(!pLooseMatch && !pLink->IsConditional() && pLink->GetLeftArg()->IsCompatible(pType))
				pLooseMatch = *pLink;
		}
		return pLooseMatch ? (pLooseMatch->IsConditional() ? nullptr : pLooseMatch) : nullptr;
	}
	COperation * GetConditionMatchingType(const CTypeBase * pType)
	{
		COperation * pLooseMatch = nullptr;
		for(auto pLink = m_Operations.Begin(); pLink != m_Operations.Bad(); ++pLink)
		{
			auto pOperation = *pLink;
			if(!pOperation->IsConditional() && pOperation->GetLeftArg()->IsStrictlyCompatible(pType))
				return pOperation;
			else if(!pLooseMatch && !pOperation->IsConditional() && pOperation->GetLeftArg()->IsCompatible(pType))
				pLooseMatch = pOperation;
		}
		return pLooseMatch ? (pLooseMatch->IsConditional() ? pLooseMatch : nullptr) : nullptr;
	}
	COperation * GetOperationMatchingTypes(const CTypeBase * pLType, const CTypeBase * pRType)
	{
		COperation * pLooseMatch = nullptr;
		int nLooseMatchScore = 0;
		for(auto pLink = m_Operations.Begin(); pLink != m_Operations.Bad(); ++pLink)
		{
			auto pOperation = *pLink;
			auto pOpLType = pOperation->GetLeftArg();
			auto pOpRType = pOperation->GetRightArg();
			bool bIsCond = pOperation->IsConditional();

			if(!bIsCond && pOpLType->GetType() == pLType && pOpRType->GetType() == pRType)
				return pOperation;
			else
			{
				int nMatchScore = 0;
				if(pOpLType->IsStrictlyCompatible(pLType))
					nMatchScore += 2;
				else if(pOpLType->IsCompatible(pLType))
					++nMatchScore;
				else continue;

				if(pOpRType->IsStrictlyCompatible(pRType))
					nMatchScore += 2;
				else if(pOpRType->IsCompatible(pRType))
					++nMatchScore;
				else continue;

				if(!pLooseMatch || nMatchScore > nLooseMatchScore)
				{
					if(!bIsCond)
					{
						pLooseMatch = pOperation;
						nLooseMatchScore = nMatchScore;
					}
				}
			}
		}
		return pLooseMatch ? (pLooseMatch->IsConditional() ? nullptr : pLooseMatch) : nullptr;
	}
	COperation * GetConditionMatchingTypes(const CTypeBase * pLType, const CTypeBase * pRType)
	{
		COperation * pLooseMatch = nullptr;
		for(auto pLink = m_Operations.Begin(); pLink != m_Operations.Bad(); ++pLink)
		{
			auto pOperation = *pLink;
			if(pOperation->IsConditional() && pOperation->GetLeftArg()->IsStrictlyCompatible(pLType) && pOperation->GetRightArg()->IsStrictlyCompatible(pRType))
				return pOperation;
			else if(!pLooseMatch)
			{
				if(pOperation->IsConditional())
				{
					if(pOperation->GetLeftArg()->IsCompatible(pLType) && pOperation->GetRightArg()->IsCompatible(pRType))
						pLooseMatch = pOperation;
				}
			}
		}
		return pLooseMatch ? (pLooseMatch->IsConditional() ? pLooseMatch : nullptr) : nullptr;
	}

	void GetOperatorString(char bBuff[MAX_OPERATOR_CHARS])
	{
		for(int i=0; i<MAX_OPERATOR_CHARS; i++) bBuff[i] = m_bOperator[i];
	}

	const char * GetOperatorString()		{return m_bOperator;}
	
	void IsAssignment(bool b)				{m_bIsAssignment = b;}
	bool IsAssignment()						{return m_bIsAssignment;}
	void IsConditional(bool b)				{m_bConditional = b;}
	bool IsConditional()					{return m_bConditional;}
};

class COperators
{
	static	char			s_nNumOperators;
	static	COperator	*	s_pOperators[MAX_OPERATORS];

public:
	static bool LoadFromFile(const char *szFilePath);

	static COperator * Add(const char *op)
	{
		if(s_nNumOperators < MAX_OPERATORS && !Get(op))
		{
			return s_pOperators[s_nNumOperators++] = new COperator(op);
		}
		return FALSE;
	}
	static COperator * Get(const char *op)
	{
		char tmp[MAX_OPERATOR_CHARS] = "";
		int n = 0, j = 0;

		*tmp = 0;

		// The random operators R* added mean this is the simplest way to 'string copy' them...
		for(int i=0; i < MAX_OPERATOR_CHARS; i++)
		{
			if(op[i] == '=')
			{
				if(i > 0)
				{
					switch(op[i-1])
					{
					case '+':
					case '-':
					case '*':
					case '/':
						tmp[n++] = '\0';
						break;
					default:
						tmp[n++] = op[i];
					}
				}
				break;
			}
			else tmp[n++] = op[i];
		}

		if(!*tmp)
		{
			if(!*op) return FALSE;
			*tmp = '=';
		}

		for(int i=0; i<s_nNumOperators; i++)
		{
			if(!strncmp(s_pOperators[i]->m_bOperator, tmp, MAX_OPERATOR_CHARS))
				return s_pOperators[i];
		}
		return FALSE;
	}

	static COperator * GetByIndex(int idx)					{return s_nNumOperators > idx ? s_pOperators[idx] : FALSE;}
	static int Count()										{return s_nNumOperators;}
};